package com.zzh.core.base;

import com.mool.xsqlbuilder.XsqlBuilder;
import com.mool.xsqlbuilder.XsqlBuilder.XsqlFilterResult;
import common.helper.OffsetLimitResultSetExtractor;
import common.page.ViewPage;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

/**
 * @author zhuxh
 * @date 16/7/26
 */
@Repository
public abstract class BaseSpringJdbcDao<T, ID extends Serializable> {

  private final String LIMIT_PLACEHOLDER = ":__limit";
  private final String OFFSET_PLACEHOLDER = ":__offset";

  private final XsqlBuilder xsqlBuilder = new XsqlBuilder();

  @Resource
  private JdbcTemplate jdbcTemplate;

  @Resource
  private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

  /**
   * 主键属性
   *
   * @return String
   */
  public abstract String getIdentifierPropertyName();

  public abstract Class getEntityClass();

  public abstract String getFindByIdSql();

  public abstract String getDeleteByIdSql();

  protected void insertWithIdentity(T t, String insertSql) {
    KeyHolder keyHolder = new GeneratedKeyHolder();
    getNamedParameterJdbcTemplate().update(insertSql,
        new BeanPropertySqlParameterSource(t), keyHolder);
    setIdentifierProperty(t, keyHolder.getKey().longValue());
  }

  private void setIdentifierProperty(Object entity, Object id) {
    try {
      BeanUtils.setProperty(entity, getIdentifierPropertyName(), id);
    } catch (Exception e) {
      throw new IllegalStateException("cannot set property value:" + id
          + " on entityClass:" + entity.getClass()
          + " by propertyName:" + getIdentifierPropertyName(), e);
    }
  }

  protected Page<T> pageQuery(String sql, Pageable pageable, RowMapper<T> rowMapper, T t) {
    sql = resolveSQL(sql, t);
    return pageQuery(sql, pageable, rowMapper);
  }

  private String resolveSQL(String sql, T t) {
    XsqlFilterResult xsqlFilterResult = xsqlBuilder.generateSql(sql, t);
    return xsqlFilterResult.getXsql();
  }

  private Page pageQuery(String sql, Pageable pageable, RowMapper<T> rowMapper) {
    long totalCount = countQuery(sql);
    int pageSize = pageable.getPageSize();
    int startRow = getStartRow(pageable);
    List result = pageQuery(sql, startRow, pageSize, rowMapper);
    return new ViewPage(result, pageable, totalCount);
  }

  private List pageQuery(String sql, int offset, int pageSize, RowMapper rowMapper) {
    String querySQL = getLimitString(sql, pageSize, OFFSET_PLACEHOLDER, LIMIT_PLACEHOLDER);
    Map paramMap = new HashMap(2);
    paramMap.put(OFFSET_PLACEHOLDER.substring(1), offset);
    paramMap.put(LIMIT_PLACEHOLDER.substring(1), pageSize);
    List list = (List) namedParameterJdbcTemplate.query(querySQL, paramMap,
        new OffsetLimitResultSetExtractor(0, Integer.MAX_VALUE, rowMapper));
    return list;
  }

  private Long countQuery(String sql) {
    String countQuerySQL = buildCountQuerySQL(sql);
    return jdbcTemplate.queryForObject(countQuerySQL, Long.class);
  }

  private String buildCountQuerySQL(String originalSQL) {
    return String.format("select count(*) from (%s) __t", originalSQL);
  }

  private int getStartRow(Pageable pageable) {
    if (pageable.getPageNumber() <= 0) {
      return 0;
    }
    return (pageable.getPageNumber() - 1) * pageable.getPageSize();
  }

  private String getLimitString(String sql, int offset, String offsetPlaceholder,
      String limitPlaceholder) {
    return offset > 0 ? sql + " limit " + offsetPlaceholder + "," + limitPlaceholder
        : sql + " limit " + limitPlaceholder;
  }

  protected JdbcTemplate getJdbcTemplate() {
    return this.jdbcTemplate;
  }

  protected NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
    return namedParameterJdbcTemplate;
  }
}
